package com.sec.android.app.myfiles.thumbnail;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;

import com.sec.android.app.myfiles.info.FileType;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.provider.PrivateMyFilesProvider;
import com.sec.android.app.myfiles.util.JUnitHandler;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * <pre>
 * File Cache manage class.
 * Store thumbnail bmp to file and store index info to DB
 * </pre>
 *
 * @author jae.bae
 */
public class FileCacheMgr extends SQLiteOpenHelper implements AbsCacheMgr {
    private static final String FILE_CACHE_NAME = "FileCache";
    private static final int FILE_CACHE_MAX_COUNT = 1024;

    private static final int sUnSupportedFileExt[] = {
            FileType.APK, FileType.PNG, FileType.GIF
    };

    private String mTableName;
    private int mMaxCacheCount;

    private int mCacheImageSize;

    private static final String DB_THREAD_NAME = "db_thread";
    private static final int ADD_CACHE = 0;
    private static final int UPDATE_CACHE = 1;
    private HandlerThread mDbThread;
    private DbThreadHandler mDbThreadHandler;

    Context mContext;
    private SQLiteDatabase mDb = null;

    public static FileCacheMgr createFileCacheMgr(Context context) {
        FileCacheMgr ret = new FileCacheMgr(context, FILE_CACHE_NAME, FILE_CACHE_MAX_COUNT, ThumbnailImp.THUMBNAIL_SIZE);
        return ret;
    }

    private FileCacheMgr(Context context, String name, int cacheCount, int cacheImageSize) {
        super(context, name + ".db", null, PrivateMyFilesProvider.DATABASE_VERSION);

        mTableName = name;
        mMaxCacheCount = cacheCount;
        mCacheImageSize = cacheImageSize;

        try {
            mDb = getWritableDatabase();
        } catch (SQLiteFullException e) {
            Log.e(this, "SQLiteFullException:" + e.toString());
        }

        mContext = context;

        mDbThread = new HandlerThread(name + DB_THREAD_NAME, Process.THREAD_PRIORITY_BACKGROUND);
        mDbThread.start();

        Looper looper = mDbThread.getLooper();

        if (looper != null) {
            mDbThreadHandler = new DbThreadHandler(looper);
        }
    }

    @Override
    public void addCache(FileRecord record, Bitmap bmp) {
        CacheInfo info = new CacheInfo();
        info.record = record;
        info.bmp = bmp;

        Message msg = new Message();
        msg.what = ADD_CACHE;
        msg.obj = info;
        mDbThreadHandler.sendMessageAtFrontOfQueue(msg);
    }

    private static final class CacheInfo {
        public FileRecord record;
        public Bitmap bmp;
    }

    private boolean isSupport(FileRecord record) {
        boolean bRet = true;
        for (int type : sUnSupportedFileExt) {
            if (record.getFileType() == type) {
                bRet = false;
                break;
            }
        }
        return bRet;
    }

    private void _AddCache(FileRecord record, Bitmap bmp) {
        if (isSupport(record)) {
            int nEmptySlot = getEmptySlot();

            if (nEmptySlot >= 0) {
                if (addToCacheFile(nEmptySlot, bmp)) {
                    AddToCacheDb(record, nEmptySlot);
                }
            }
        }
    }

    @Override
    public Bitmap getCache(FileRecord record) {
        Bitmap bmpRet = null;

        if (isSupport(record)) {
            int nSlot = getSlot(record);

            if (nSlot >= 0) {
                bmpRet = getBmpFromCacheFile(nSlot);
            }
        }

        return bmpRet;
    }

    /**
     * clear all information of thumbnail
     */
    public void clearDB() {
        String strQuery = "update " + mTableName + " set storage = 0,  path = '', size = 0,  date = 0, latest = 0 ";
        try {
            if (mDb != null) {
                mDb.execSQL(strQuery);
            }
        } catch (SQLiteFullException e) {
            Log.e(this, "SQLiteFullException:" + e.toString());
        }
    }

    /**
     * get empty slot or in the order that has not been recently used
     *
     * @return slot number of deleted record
     * @note cache file does not need to remove.
     */
    private int getEmptySlot() {
        int nRet = -1;
        String strQuery = "select _index from " + mTableName + " order by latest";
        if (mDb != null) {
            try (Cursor cursor = mDb.rawQuery(strQuery, null)) {
                cursor.moveToNext();
                nRet = cursor.getInt(0);
            }
        }
        return nRet;
    }


    /**
     * get cache index of record
     *
     * @param record
     * @return slot number of record in cache
     */
    private int getSlot(FileRecord record) {
        int nRet = -1;

        String strQuery = "select _index from " + mTableName + " where " + " path = ?" + " and storage = " + record.getStorageType().ordinal()
                + " and size = " + record.getSize() + " and date = " + record.getDate();
        if (mDb != null) {
            try (Cursor cursor = mDb.rawQuery(strQuery, new String[]{
                    record.getFullPath()})) {

                if (cursor.moveToNext()) {
                    nRet = cursor.getInt(0);
                    updateSlot(nRet);
                }
            }
        }
        return nRet;

    }

    /**
     * update latest access time to now
     *
     * @param nSlot
     */
    private void updateSlot(int nSlot) {
        Message msg = new Message();
        msg.what = UPDATE_CACHE;
        msg.arg1 = nSlot;
        mDbThreadHandler.sendMessageAtFrontOfQueue(msg);
    }

    /**
     * update latest access time to now
     *
     * @param nSlot
     */
    private void _updateSlot(int nSlot) {
        String[] args = {String.valueOf(System.currentTimeMillis()), String.valueOf(nSlot)};
        String strQuery = "update " + mTableName + " set " + "latest =? where _index =?;";
        try {
            if (mDb != null) {
                mDb.execSQL(strQuery, args);
            }
        } catch (SQLiteFullException e) {
            Log.e(this, "SQLiteFullException:" + e.toString());
        }
    }

    long totTime = 0;
    int cnt = 0;

    private boolean addToCacheFile(int nEmptySlot, Bitmap bmp) {
        boolean bRet = false;

        long time = SystemClock.elapsedRealtime();
        if (bmp != null) {
            String fileName = mContext.getCacheDir() + "/" + nEmptySlot + ".jpg";

            try (FileOutputStream out = new FileOutputStream(fileName)) {
                bRet = bmp.compress(Bitmap.CompressFormat.JPEG, 80, out);
            } catch (IOException e) {
                Log.e(this, "IOException:" + e.toString());
            }
        }

        time = SystemClock.elapsedRealtime() - time;
        totTime += time;
        cnt++;

        Log.d(this, "addToCacheFile " + bRet + "  time : " + time + " / avg :" + totTime / cnt);

        return bRet;
    }

    private boolean AddToCacheDb(FileRecord record, int nEmptySlot) {
        String strQuery = "update " + mTableName + " set " + "storage = " + record.getStorageType().ordinal() + ", " + "path = ?, " + "size = "
                + record.getSize() + ", " + "date = " + record.getDate() + ", " + "latest = " + System.currentTimeMillis() + " where _index = "
                + nEmptySlot;
        try {
            if (mDb != null) {
                mDb.execSQL(strQuery, new String[]{
                        record.getFullPath()});
            }
        } catch (SQLiteFullException e) {
            Log.e(this, "SQLiteFullException:" + e.toString());
        }
        return true;
    }

    private Bitmap getBmpFromCacheFile(int nSlot) {
        Bitmap bmpRet = null;

        String fileName = mContext.getCacheDir() + "/" + nSlot + ".jpg";

        try (FileInputStream stream = new FileInputStream(fileName)) {
            FileDescriptor fd = stream.getFD();

            BitmapFactory.Options options = ThumbnailImp.getBmpFactoryOption(fd, mCacheImageSize);
            if (options != null) {
                bmpRet = BitmapFactory.decodeFileDescriptor(fd, null, options);
            }

        } catch (FileNotFoundException e) {
            Log.e(this, "FileNotFoundException:" + e.toString());
        } catch (IOException e) {
            Log.e(this, "IOException:" + e.toString());
        }

        return bmpRet;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        onUpgrade(db, 0, PrivateMyFilesProvider.DATABASE_VERSION);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion <= newVersion) {
            try {
                String strQuery = "DROP TABLE IF EXISTS " + mTableName;
                db.execSQL(strQuery);

                strQuery = "CREATE TABLE IF NOT EXISTS " + mTableName + " ( " + "_index INTEGER PRIMARY KEY AUTOINCREMENT, " + "storage int, "
                        + "path text, " + "size int, " + "date int, " + "latest long " + " ) ";
                db.execSQL(strQuery);

                db.beginTransaction();
                for (int i = 0; i < mMaxCacheCount; i++) { // insert dummy data
                    strQuery = "insert into " + mTableName + "( latest ) values ( " + i + ")";
                    db.execSQL(strQuery);
                }
                db.setTransactionSuccessful();
            } catch (SQLiteFullException e) {
                Log.e(this, "SQLiteFullException:" + e.toString());
            } finally {
                db.endTransaction();
            }
        }
    }

    private final class DbThreadHandler extends JUnitHandler<CacheInfo> {
        public DbThreadHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case ADD_CACHE:
                    CacheInfo info = getMsgValue(msg.obj);
                    if (info != null) {
                        _AddCache(info.record, info.bmp);
                    }
                    break;
                case UPDATE_CACHE:
                    _updateSlot(msg.arg1);
                    break;
            }
        }

    }
}
